<?php

	namespace org\tekuna\core\event;
	
	use org\tekuna\base\Tekuna;
	
	use org\tekuna\core\context\Context;
	use org\tekuna\core\context\ApplicationContextAware;
	use org\tekuna\core\util\WeightedList;
	
	
	/**
	 * This class manages all Events. Here EventListeners can be 
	 * registered and Events triggered. When an Event is triggered
	 * all suitable EventListeners will be called.
	 * 
	 * The order of the EventListener calls is determined by a
	 * WeightenedQueue, so min and max weights are possible here.
	 */
	class EventManager implements ApplicationContextAware {
	
		/** When registering an EventListener without a weight,
		 *  this default weight is used. */
		const DEFAULT_LISTENER_WEIGHT = 10;
		
		
		protected
			$objContext,
			$objListenerList,
			$objLogger,
			
			// lists for monitoring purposes
			$arrRawListenerList = array(),
			$arrEventHistory = array();
		
		
		/**
		 * Construct a new EventManager with an empty WeightedList inside.
		 */
		public function __construct() {

			$this -> objListenerList = new WeightedList();
			$this -> objLogger = Tekuna :: getLogger(__CLASS__);
		}
		
		
		/**
		 * (non-PHPdoc)
		 * @see src/org/tekuna/core/context/org\tekuna\core\context.ApplicationContextAware::setApplicationContext()
		 */
		public function setApplicationContext(Context $objContext) {
			
			$this -> objContext = $objContext;
		}
		
		
		/**
		 * (non-PHPdoc)
		 * @see src/org/tekuna/core/context/org\tekuna\core\context.ApplicationContextAware::getApplicationContext()
		 */
		public function getApplicationContext() {
			
			return $this -> objContext;
		}

		
		/**
		 * Register a new EventListener.
		 * 
		 * @param EventListener $objListener the listener to register
		 * @param mixed $sWeight the weight of the listener 
		 */
		public function registerListener(EventListener $objListener, $sWeight = self :: DEFAULT_LISTENER_WEIGHT) {
		
			// supply all currently available context objects
			$this -> objContext -> autowireObject($objListener);
			
			// put listener in listener list
			$this -> objListenerList -> addElement($objListener, $sWeight);
			$this -> arrRawListenerList[] = array('listener' => $objListener, 'weight' => $sWeight);
			$this -> objLogger -> info("Registered new EventListener: ". get_class($objListener));
		}
		
		
		/**
		 * Propagate a certain event to all suitable EventListeners.
		 * 
		 * @param $objEvent the event to publish
		 * @return boolean true, if at least one EventListener's handleEvent
		 *         method was called.
		 */
		public function triggerEvent(Event $objEvent) {
			
			$this -> objLogger -> info("Triggered Event: ". get_class($objEvent));
			
			// register event in the event history
			static $arrHandledListenerStack = array();
			$objTriggeredEvent = new TriggeredEvent($objEvent);
			if (isset($arrHandledListenerStack[0])) {
				
				$arrHandledListenerStack[0] -> addTriggeredEvent($objTriggeredEvent);
			}
			else {
				
				$this -> arrEventHistory[] = $objTriggeredEvent;
			}
			
			// iterate event listeners
			$bEventHandled = false;
			foreach ($this -> objListenerList -> getSortedList() as $objListener) {
				
				if ($objListener -> handlesEvent($objEvent)) {
					
					// register listener in the event listener history
					$objHandledListener = new HandledListener($objListener);
					array_unshift($arrHandledListenerStack, $objHandledListener);
					$objTriggeredEvent -> addHandledListener($objHandledListener);
					$this -> objLogger -> info("Event ". get_class($objEvent) .' will be handled by EventListener '. get_class($objListener));
					
					// actually handle the event
					$objListener -> handleEvent($objEvent);
					$bEventHandled = true;
					
					array_shift($arrHandledListenerStack);
				}
			}
			
			return $bEventHandled;
		}
		
		
		/**
		 * @return array Returns an array that holds references to all 
		 * 	       registered listeners in the correct order of 
		 *         invocation (according to the registration weight).
		 */
		public function getRegisteredListeners() {
			
			return $this -> objListenerList -> getSortedList();
		}
		
		
		/**
		 * @return array Returns the list of listeners in the order of
		 *         their registration.
		 */
		public function getRawListenersList() {
			
			return $this -> arrRawListenerList;
		}
		
		
		/**
		 * @return array the list of TriggeredEvent objects
		 */
		public function getEventsHistory() {
			
			return $this -> arrEventHistory;
		}
	}
	
	
	class TriggeredEvent {
		
		private
			$objEvent = NULL,
			$arrHandledListeners = array();
			
		public function __construct(Event $objEvent) {
			
			$this -> objEvent = $objEvent;
		}
		
		public function addHandledListener(HandledListener $objHandledListener) {
			
			$this -> arrHandledListeners[] = $objHandledListener;
		}
		
		public function getEvent() {
			
			return $this -> objEvent;
		}
		
		public function getHandledListeners() {
			
			return $this -> arrHandledListeners;
		}
	}
	
	
	class HandledListener {
		
		private
			$objListener = NULL,
			$arrTriggeredEvents = array();
		
		public function __construct(EventListener $objListener) {
			
			$this -> objListener = $objListener;
		}
		
		public function addTriggeredEvent(TriggeredEvent $objTriggeredEvent) {

			$this -> arrTriggeredEvents[] = $objTriggeredEvent;
		}
		
		public function getListener() {
			
			return $this -> objListener;
		}
		
		public function getTriggeredEvents() {
			
			return $this -> arrTriggeredEvents;
		}
	}